<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2019/1/5 0005
 * Time: 19:56
 */

namespace timer_job\common\ext;

use think\Exception;

/**
 * redis封装，用于实现多进程
 * 的redis不断链和锁机制支持
 *
 * Class RedisServer
 * @package job\common\ext
 */
class RedisServer {
    private  $_redis = null; // 原生的redis实例，可以用它调用原生的php redis操作
    const LOCK_EXPIRE = 3500; // 锁过期时间，毫秒
    const LOCK_PREFIX = 'LOCK_REDIS_KEY_'; // redis用于锁的键前缀

    public function __construct()
    {
        $config = config('cache.redis');//redis配置
        $host = $config['host'];
        $pwd = $config['password'];
        $redis = new \Redis();
        $redis->connect($host);
        $redis->auth($pwd);
        $this->_redis = $redis;
    }

    /**
     * 检测redis的连接是否断开
     *
     * @return bool
     */
    public  function checkRedis() {
        try {
            $res = $this->_redis->ping();
            if ($res == 'PONG') {
                return true;
            }
            return false;
        } catch (\Exception $e) {
            return false;
        }
    }

    // 不加锁获取
    public function get($name) {
        $redis = $this->_redis;
        $value = $redis->get($name);
        if ($value) {
            $arr = @json_decode($value, true);
            if (is_array($arr)) {
                return $arr;
            } else {
                return $value;
            }
        }
    }

    /**
     * 返回原生的redis对象
     *
     * @return object \Redis
     */
    public function getNativeRedis() {
        return $this->_redis;
    }

    /**
     * 默认是加锁，is_lock为false时是不加锁
     *
     * @param $name
     * @param $value
     * @param null $expire
     * @param bool $is_lock
     * @return bool
     */
    public function set($name, $value, $expire = null, $is_lock = true) {
        $redis = $this->_redis;
        if (is_array($value)) {
            $value = json_encode($value);
        }
        // 不加锁
        if ($is_lock == false) {
            if ($expire) {
                return $redis->set($name, $value, $expire);
            }
            return $redis->set($name, $value);
        }

        // 加锁
        while(!$redis->setnx(self::LOCK_PREFIX. $name, self::mSecTime()+self::LOCK_EXPIRE)){
            if($this->mSecTime()>$redis->get(self::LOCK_PREFIX . $name) && self::mSecTime()>$redis->getset(self::LOCK_PREFIX . $name, self::mSecTime()+self::LOCK_EXPIRE)){
                break;
            }
        }
        if ($expire) {
            $res = $redis->set($name, $value, $expire);
        } else {
            $res = $redis->set($name, $value);
        }
        $this->rm(self::LOCK_PREFIX . $name);
        return $res;

    }


    /**
     * 不加锁的设置键值
     *
     * @param $name
     * @param $value
     * @param null $expire
     * @return bool
     */
    public function originalSet($name, $value, $expire = null) {
        $redis = $this->_redis;
        if (is_array($value)) {
            $value = json_encode($value);
        }

        if ($expire) {
            return $redis->set($name, $value, $expire);
        }
        return $redis->set($name, $value);
    }

    /**
     * 从redis里移除
     *
     * @param $name
     * @return bool
     */
    public function rm($name) {
        $redis = $this->_redis;
        if ($redis->del($name)) {
            return true;
        }
        return false;
    }

    /**
     * 获取锁，这是模拟行锁机制
     * 并执行回调里的业务
     * callback和data参数遵循call_user_func_array机制
     *
     * @param $key
     * @param $callBack
     * @param null $data
     * @return mixed
     * @throws Exception
     */
    public function getLockExecute($key, $callBack, $data = null) {
        $redis = $this->_redis;
        if (!is_array($data)) {
            throw new Exception('第三个参数必须是数组，用于回调函数');
        }

        while(!$redis->setnx(self::LOCK_PREFIX. $key, self::mSecTime()+self::LOCK_EXPIRE)){
            if(self::mSecTime()>$redis->get(self::LOCK_PREFIX . $key) && self::mSecTime()>$redis->getset(self::LOCK_PREFIX . $key, self::mSecTime()+self::LOCK_EXPIRE)){
                break;
            }
        }
        $res = call_user_func_array($callBack, $data);
        $this->rm(self::LOCK_PREFIX . $key);
        return $res;
    }

    /**
     * 加锁修改或新增redis里键为key的数据
     *
     * @param $key
     * @param $value
     * @param null $expire
     * @return bool
     */
    public function setLockKey($key, $value, $expire = null) {
        $redis = $this->_redis;
        while(!$redis->setnx(self::LOCK_PREFIX. $key, self::mSecTime()+self::LOCK_EXPIRE)){
            if($this->mSecTime()>$redis->get(self::LOCK_PREFIX . $key) && $this->mSecTime()>$redis->getset(self::LOCK_PREFIX . $key, $this->mSecTime()+self::LOCK_EXPIRE)){
                break;
            }
        }

        if ($expire) {
            $res = $this->originalSet($key, $value, $expire);
        } else {
            $res = $this->originalSet($key, $value);
        }
        $this->rm(self::LOCK_PREFIX . $key);
        return $res;
    }

    /**
     * 加锁访问获取key的值
     *
     * @param $key
     * @return bool|mixed|string
     */
    public function getLockKey($key) {
        $redis = $this->_redis;
        while(!$redis->setnx(self::LOCK_PREFIX. $key, self::mSecTime()+self::LOCK_EXPIRE)){
            if(self::mSecTime()>$redis->get(self::LOCK_PREFIX . $key) && self::mSecTime()>$redis->getset(self::LOCK_PREFIX . $key, self::mSecTime()+self::LOCK_EXPIRE)){
                break;
            }
        }
        $val = $this->get($key);
        $this->rm(self::LOCK_PREFIX . $key);
        return $val;
    }


    /**
     * 给键加锁与unlock连用，锁住该键，再lock与unlock间执行锁住后的代码逻辑，注意一定要及时释放锁（提高性能）
     * 这里尤其注意，在lock与unlock间再对该锁定的键进行redis操作，一定不能再加锁操作，否则会死锁影响性能，
     * 因为锁的过期时间为5秒，如：
     * lock（$key）
     * to do ....
     * $redis->set($key, $value) // 这里是加锁设值的，所以会死锁，要用$redis->originalSet($key, $value);
     * $redis->getLockKey($key); // 这里也会死锁，要用$redis->get($key);
     * unlock($key)
     *
     * @param $key
     */
    public function lock($key) {
        $redis = $this->_redis;
        while(!$redis->setnx(self::LOCK_PREFIX. $key, self::mSecTime()+self::LOCK_EXPIRE)){
            if(self::mSecTime()>$redis->get(self::LOCK_PREFIX . $key) && self::mSecTime()>$redis->getset(self::LOCK_PREFIX . $key, self::mSecTime()+self::LOCK_EXPIRE)){
                break;
            }
        }
    }

    // 解开锁与lock连用
    public function unlock($key) {
        $this->rm(self::LOCK_PREFIX . $key);
    }

    /**
     * 返回当前的毫秒时间戳
     *
     * @return float
     */
    public static function mSecTime() {
        list($msec, $sec) = explode(' ', microtime());
        return $msectime = (float)sprintf('%.0f', (floatval($msec) + floatval($sec)) * 1000);
    }
}